/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
/*
pin mapping
SPI_FLASH   8010H
CLK         PC0
CS#         PC1
DI          PC2
DO          PC3
VCC         VDDIO
VSS         GND
*/
#include "drv_pin.h"
#include "drv_spi.h"
// #include "driver_plf.h"
// #include "hard_spi.h"
// #include "driver_flash_ssp.h"
// #include "driver_system.h"
// #include "driver_iomux.h"
// #include "sys_utils.h"
#include "zino.h"
#include "w25qxx.h"
#include <string.h>

#define DBG_TAG "w25qxx"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#define W25QXX_CS_PIN PB12
// #define W25QXX_HOLD_PIN

// #include "os_timer.h"
/**
 * @brief
 * Page Program Time tPP 0.7 5 ms
 * Sector Erase Time (4KB) tSE 100 400 ms
 * Block Erase Time (32KB) tBE1 120 1,600 ms
 * Block Erase Time (64KB) tBE2 150 2,000 ms
 * Chip Erase Time tCE 40 200 s
 *
 */
#define W25Q128_PAGE_SIZE (0x100U) //256B
#define W25Q128_SECTOR_SIZE (0X1000U) //4KB
#define W25Q128_SECTOR_COUNT (16U) //1024
#define W25Q128_BLOCK_SIZE (W25Q128_SECTOR_SIZE * W25Q128_SECTOR_COUNT) //64KB
#define W25Q128_BLOCK_COUNT (2048U)
#define W25Q128_CHIP_SIZE (W25Q128_BLOCK_SIZE * W25Q128_BLOCK_COUNT) //128MB

// static struct fr_spi_device spi_25qxx;
// static os_timer_t w25qxx_timer;
spi_dev_t spi_25qxx = {
    .instance = SPI2,
    .cs_pin = PB12,
    .mode = RT_SPI_MODE_3 | RT_SPI_MSB,
    .data_width = 8,
    .max_hz = 10 * 1000 * 1000,
};

static uint8_t w25qxx_buf[W25Q128_PAGE_SIZE];
static uint8_t w25qxx_buf_4k[W25Q128_SECTOR_SIZE];

// static void w25qxx_cs_ctrl(uint8_t state)
// {
//     pin_write(W25QXX_CS_PIN, state);
//     // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_1, state);
//     // pin_mode
// }
static void w25qxx_hardware_reset(void)
{
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_5, 1);
    // co_delay_10us(1);
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_5, 0);
    // co_delay_10us(1);
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_5, 1);
    // co_delay_10us(1);
}
void w25qxx_spi_init(void)
{
    // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_0, PORTC0_FUNC_SSP0_CLK);
    // // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_1, PORTC1_FUNC_SSP0_CSN);
    // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_2, PORTC2_FUNC_SSP0_DOUT);
    // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_3, PORTC3_FUNC_SSP0_DIN);
    // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_5, PORTC5_FUNC_C5);//HOLD PIN
    // system_set_port_mux(GPIO_PORT_C, GPIO_BIT_6, PORTC6_FUNC_C6);//WP PIN

    // gpio_set_dir(GPIO_PORT_C, GPIO_BIT_1, GPIO_DIR_OUT);
    // gpio_set_dir(GPIO_PORT_C, GPIO_BIT_5, GPIO_DIR_OUT);
    // gpio_set_dir(GPIO_PORT_C, GPIO_BIT_6, GPIO_DIR_OUT);
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_1, 1);
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_5, 1);
    // gpio_set_pin_value(GPIO_PORT_C, GPIO_BIT_6, 1);

    // fr_spi_init(&spi_25qxx, 8, 24000000, 3, w25qxx_cs_ctrl);

    spi_dev_init(&spi_25qxx);
    LOG_D("w25qxx_spi_init\r\n");
    // spi2_dma_config();
}

/**
 * @brief Wait for processing done.
 *
 * @return int 0:ok, -1:timeout
 */
int w25qxx_WaitProcessDone(void)
{
    uint16_t timeout = 500;
    while (timeout--)
    {
        uint8_t ret = w25qxx_ReadStatus(W25QXX_READ_STATUS_REGISTER_1);
        LOG_E("W25QXX_READ_STATUS_REGISTER_1:%x\r\n", ret);
        if (!(ret & W25QXX_FLAG_BUSY))
        {
            return 0;
        }
        rt_thread_mdelay(1);
        LOG_E(".");
    }
    return -1;
}
void w25qxx_WriteCmd(uint8_t cmd, uint8_t* cmdData, uint32_t cmdDataLen)
{
    spi_send_then_send(&spi_25qxx, &cmd, 1, cmdData, cmdDataLen);
}
void w25qxx_ReadCmd(uint8_t cmd, uint8_t* cmdData, uint32_t cmdDataLen, uint8_t* data, uint32_t dataLen)
{
    uint8_t tx[8];
    tx[0] = cmd;
    memcpy(&tx[1], cmdData, cmdDataLen);
    spi_send_then_receive(&spi_25qxx, tx, cmdDataLen + 1, data, dataLen);
}
void w25qxx_WriteEnable(void)
{
    w25qxx_WriteCmd(W25QXX_WRITE_ENABLE, 0, 0);
}
void w25qxx_WriteDisable(void)
{
    w25qxx_WriteCmd(W25QXX_WRITE_DISABLE, 0, 0);
}
uint16_t w25qxx_GetManDeviceId(void)
{
    uint8_t tempId[2];
    uint8_t DummyByte[3] = { 0 };
    w25qxx_ReadCmd(W25QXX_MANUFACTURER_DEVICE_ID, DummyByte, 3, tempId, 2);
    return tempId[0] << 8 | tempId[1];
}
/**
 * @brief Read unique 64-bit ID.
 *
 * @param id
 */
void w25qxx_ReadUniqueID(uint8_t* id)
{
    uint8_t DummyByte[3] = { 0 };
    w25qxx_ReadCmd(W25QXX_READ_UNIQUE_ID, DummyByte, 4, id, 8);
}
/**
 * @brief Read status register.
 *
 * @param RdCmd
 *   @arg  W25QXX_READ_STATUS_REGISTER_1: Read status register 1.
 *   @arg  W25QXX_READ_STATUS_REGISTER_2: Read status register 2.
 *   @arg  W25QXX_READ_STATUS_REGISTER_3: Read status register 3.
 * @return uint8_t status
 */
uint8_t w25qxx_ReadStatus(uint8_t RdCmd)
{
    uint8_t status;
    w25qxx_ReadCmd(RdCmd, 0, 0, &status, 1);
    return status;
}
/**
 * @brief  Write status register.
 *
 * @param WtCmd
 *   @arg  W25QXX_WRITE_STATUS_REGISTER_1: Write status register 1.
 *   @arg  W25QXX_WRITE_STATUS_REGISTER_2: Write status register 2.
 *   @arg  W25QXX_WRITE_STATUS_REGISTER_3: Write status register 3.
 * @param status
 */
void w25qxx_WriteStatus(uint8_t WtCmd, uint8_t status)
{
    w25qxx_WriteCmd(WtCmd, &status, 1);
}
void w25qxx_PowerDown(void)
{
    w25qxx_WriteCmd(W25QXX_POWER_DOWN, 0, 0);
}
void w25qxx_ReleasePowerDown(void)
{
    w25qxx_WriteCmd(W25QXX_RELEASE_POWER_DOWN_ID, 0, 0);
}
void w25qxx_EraseChip(void)
{

    w25qxx_WriteCmd(W25QXX_CHIP_ERASE, 0, 0);
}
void w25qxx_EraseSector(uint32_t SectorAddr)
{
    uint8_t tx[3] = { SectorAddr >> 16,SectorAddr >> 8,SectorAddr};
    // LOG_E("E:%x\r\n", SectorAddr);
    w25qxx_WriteEnable();
    // w25qxx_WaitProcessDone();
    w25qxx_WriteCmd(W25QXX_SECTOR_ERASE, tx, 3);
}
void w25qxx_FastRead(uint32_t ReadAddr, uint8_t* ReadBuf, uint16_t ReadSize)
{
    uint8_t tx[5] = { W25QXX_FAST_READ, ReadAddr >> 16,ReadAddr >> 8,ReadAddr, 0x55 };
    // w25qxx_ReadCmd(W25QXX_FAST_READ, tx, 5, ReadBuf, ReadSize);
    spi_send_then_receive(&spi_25qxx, tx, 5, ReadBuf, ReadSize);
}
void w25qxx_ReadData(uint32_t ReadAddr, uint8_t* ReadBuf, uint32_t ReadSize)
{
    uint8_t tx[4] = { W25QXX_READ_DATA, ReadAddr >> 16,ReadAddr >> 8,ReadAddr };
    spi_send_then_receive(&spi_25qxx, tx, 4, ReadBuf, ReadSize);
    // w25qxx_ReadCmd(W25QXX_READ_DATA, tx, 3, ReadBuf, ReadSize);
}
/**
 * @brief write one page flash data
 *
 * @param WriteAddr
 * @param WriteBuf
 * @param WriteSize 0~256
 */
void w25qxx_PageProgram(uint32_t WriteAddr, uint8_t* WriteBuf, uint32_t WriteSize)
{
    uint8_t tx[4] = { W25QXX_PAGE_PROGRAM, WriteAddr >> 16,WriteAddr >> 8,WriteAddr };

    if (((WriteAddr & 0xff) + WriteSize) > 256)
    {
        uint8_t firstWrite = 256 - (WriteAddr & 0xff);
        w25qxx_WriteEnable();
        spi_send_then_send(&spi_25qxx, tx, 4, WriteBuf, firstWrite);
        // LOG_E("first adr:%08x, size:%d, buf:%08x\r\n",WriteAddr, firstWrite, WriteBuf);

        WriteAddr += firstWrite;//next page addr
        WriteBuf += firstWrite;
        WriteSize -= firstWrite;
        tx[0] = W25QXX_PAGE_PROGRAM;
        tx[1] = WriteAddr >> 16;
        tx[2] = WriteAddr >> 8;
        tx[3] = WriteAddr;
        w25qxx_WaitBusy();
        w25qxx_WriteEnable();
        spi_send_then_send(&spi_25qxx, tx, 4, WriteBuf, WriteSize);//write next page data
        // LOG_E("second adr:%08x, size:%d, buf:%08x",WriteAddr, WriteSize, WriteBuf);
    }
    else
    {
        w25qxx_WriteEnable();
        spi_send_then_send(&spi_25qxx, tx, 4, WriteBuf, WriteSize);
    }

}
void w25qxx_SectorProgram_4K(uint32_t WriteAddr, uint8_t* WriteBuf, uint32_t WriteSize)
{
    uint32_t pageCount = WriteSize / W25Q128_PAGE_SIZE;
    LOG_E("w:%x %d %d\r\n", WriteAddr, pageCount, WriteSize);
    if (pageCount)
    {
        while (pageCount)
        {
            w25qxx_WriteEnable();
            w25qxx_WaitBusy();
            // LOG_E("addr:%x, size:%d\r\n", WriteAddr, W25Q128_PAGE_SIZE);
            w25qxx_PageProgram(WriteAddr, WriteBuf, W25Q128_PAGE_SIZE);
            w25qxx_WaitBusy();
            WriteAddr += W25Q128_PAGE_SIZE;
            WriteBuf += W25Q128_PAGE_SIZE;
            WriteSize -= W25Q128_PAGE_SIZE;
            pageCount--;
        }
    }
    if (WriteSize)
    {
        w25qxx_WriteEnable();
        w25qxx_WaitBusy();
        // LOG_E("addr:%x, size:%d\r\n", WriteAddr, WriteSize);
        w25qxx_PageProgram(WriteAddr, WriteBuf, WriteSize);
    }

}
void w25qxx_FlashProgram(uint32_t WriteAddr, uint8_t* WriteBuf, uint32_t WriteSize)
{

}
void w25qxx_WaitBusy()
{
    uint16_t waitTimeOut = 1000;
    while (waitTimeOut--)
    {
        uint8_t ret = w25qxx_ReadStatus(W25QXX_READ_STATUS_REGISTER_1);
        if ((ret & W25QXX_FLAG_BUSY) == 0)
        {
            return;
        }
        rt_thread_mdelay(1);
    }
    // LOG_E("w25qxx timeout! \r\n");
}
bool w25qxx_IsBusy()
{
    uint8_t ret = w25qxx_ReadStatus(W25QXX_READ_STATUS_REGISTER_1);
    LOG_E("busy:%x\r\n", ret);
    return (ret & W25QXX_FLAG_BUSY);
}
void w25qxx_task(void* p)
{
    enum W25QXX_State_e {
        W25QXX_STATE_IDLE,
        W25QXX_STATE_SECTOR_ERASING,
        W25QXX_STATE_SECTOR_WRITE,
        W25QXX_STATE_SECTOR_WRITING,
        W25QXX_STATE_SECTOR_WRITE_DONE,

    };
    static enum W25QXX_State_e W25QXX_State = 0;
    static uint32_t SectorAddr = 0;
    switch (W25QXX_State)
    {
    case W25QXX_STATE_IDLE:
        SectorAddr = 0;
        LOG_E("start sector erasing...\r\n");
        // w25qxx_WriteEnable();
        w25qxx_EraseSector(SectorAddr);
        W25QXX_State = W25QXX_STATE_SECTOR_ERASING;
        break;
    case W25QXX_STATE_SECTOR_ERASING:
        if (!w25qxx_IsBusy())
        {
            LOG_E("sector erase done %d/%d\r\n", SectorAddr, W25Q128_PAGE_SIZE);
            // SectorAddr += W25Q128_SECTOR_SIZE;
            // if (SectorAddr < W25Q128_BLOCK_SIZE)
            // {
            //     w25qxx_WriteEnable();
            //     w25qxx_EraseSector(SectorAddr);
            // }
            // else
            {
                LOG_E("chip erase done!\r\n");
                W25QXX_State = W25QXX_STATE_SECTOR_WRITE;
                w25qxx_FastRead(0, w25qxx_buf, W25Q128_PAGE_SIZE);
                LOG_E("fast read test:\r\n");

                for (int i = 0;i < W25Q128_PAGE_SIZE;i++)
                {
                    LOG_E("%X ", w25qxx_buf[i]);
                    if ((i % 32) == 31)LOG_E("[%d]\r\n", i);
                }
                LOG_E("\r\n");
                w25qxx_WriteEnable();
            }
        }
        break;
    case W25QXX_STATE_SECTOR_WRITE:
        LOG_E("start sector writing...\r\n");
        for (int i = 0;i < W25Q128_SECTOR_SIZE;i++)
        {
            w25qxx_buf_4k[i] = i;
        }
        // w25qxx_PageProgram(0, w25qxx_buf, W25Q128_PAGE_SIZE);
        w25qxx_SectorProgram_4K(0, w25qxx_buf_4k, W25Q128_SECTOR_SIZE);
        W25QXX_State = W25QXX_STATE_SECTOR_WRITING;
        break;
    case W25QXX_STATE_SECTOR_WRITING:
        if (!w25qxx_IsBusy())
        {
            memset(w25qxx_buf_4k, 0, W25Q128_SECTOR_SIZE);
            w25qxx_FastRead(0, w25qxx_buf_4k, W25Q128_SECTOR_SIZE);
            for (int i = 0;i < W25Q128_SECTOR_SIZE;i++)
            {
                LOG_E("%X ", w25qxx_buf_4k[i]); if ((i % 32) == 31)LOG_E("[%d]\r\n", i);
                // if (w25qxx_buf_4k[i] != i)
                // {
                //     LOG_E("write error! at[%d]:%d\r\n", i, w25qxx_buf_4k[i]);
                //     // break;;
                // }
            }
            LOG_E("W/R test ok!\n\r");
        }
        W25QXX_State = W25QXX_STATE_SECTOR_WRITE_DONE;



        break;
    default:
        break;
    }
}

int w25qxx_init()
{
    w25qxx_spi_init();
    // uint32_t id = w25qxx_read_JEDEC_ID();
    w25qxx_hardware_reset();
    uint16_t id = w25qxx_GetManDeviceId();
    switch (id)
    {
    case W25Q80:
        LOG_I("w25q80 init W25Q80\r\n");
        break;
    case W25Q16:
        LOG_I("w25q16 init W25Q16\r\n");
        break;
    case W25Q32:
        LOG_I("w25q32 init W25Q32\r\n");
        break;
    case W25Q64:
        LOG_I("w25q64 init W25Q64\r\n");
        break;
    case W25Q128:
        LOG_I("w25q128 init W25Q128\r\n");
        break;
    default:
        LOG_I("w25qxx init error, unknow id:0x%X\r\n", id);
        return -1;
    }

    // os_timer_init(&w25qxx_timer, w25qxx_task, 0);
    // os_timer_start(&w25qxx_timer, 10, 1);

    LOG_D("w25qxx_init...\r\n");
    return 0;
}

void my_flash_read(uint32_t offset, uint32_t length, uint8_t* buffer)
{
    // void w25qxx_ReadData(uint32_t ReadAddr, uint8_t *ReadBuf, uint32_t ReadSize)
    LOG_E("w25qxx_ReadData: offset:%d lenght:%d\r\n",offset,length);
    w25qxx_ReadData(offset, buffer, length);
}
// ZINO_INIT_APP_EXPORT(w25qxx_init);
MSH_CMD_EXPORT(w25qxx_init, w25qxx init);
